TOOL.Category           = "Construction"
TOOL.Name                       = "#Stacker"
TOOL.Command            = nil
TOOL.ConfigName         = ""

TOOL.ClientConVar[ "freeze" ]           = "1"
TOOL.ClientConVar[ "weld" ]             = "1"
TOOL.ClientConVar[ "nocollide" ]        = "1"
TOOL.ClientConVar[ "mode" ]             = "1"
TOOL.ClientConVar[ "dir" ]              = "1"
TOOL.ClientConVar[ "count" ]            = "1"
TOOL.ClientConVar[ "model" ]            = ""
TOOL.ClientConVar[ "offsetx" ]          = "0"
TOOL.ClientConVar[ "offsety" ]          = "0"
TOOL.ClientConVar[ "offsetz" ]          = "0"
TOOL.ClientConVar[ "rotp" ]             = "0"
TOOL.ClientConVar[ "roty" ]             = "0"
TOOL.ClientConVar[ "rotr" ]             = "0"
TOOL.ClientConVar[ "recalc" ]           = "0"

// Add Default Language translation (saves adding it to the txt files)
if ( CLIENT ) then

        language.Add( "Tool_ol_stacker_name", "Stacker" )
        language.Add( "Tool_ol_stacker_desc", "Stack props easily" )
        language.Add( "Tool_ol_stacker_0", "Click to stack the prop you are pointing at." )
        
        language.Add( "Undone_ol_stacker", "Undone Stack" )
        
end

function TOOL:LeftClick( trace )
        if !trace.Entity then return false end
        if !trace.Entity:IsValid() then return false end
        if trace.Entity:GetClass() != "prop_physics" then return false end
        if CLIENT then return true end
        
        local freeze            = self:GetClientNumber( "freeze" ) == 1
        local weld              = self:GetClientNumber( "weld" ) == 1
        local nocollide         = self:GetClientNumber( "nocollide" ) == 1
        local mode              = self:GetClientNumber( "mode" )
        local dir               = self:GetClientNumber( "dir" )
        local count             = self:GetClientNumber( "count" )
        local offsetx           = self:GetClientNumber( "offsetx" )
        local offsety           = self:GetClientNumber( "offsety" )
        local offsetz           = self:GetClientNumber( "offsetz" )
        local rotp              = self:GetClientNumber( "rotp" )
        local roty              = self:GetClientNumber( "roty" )
        local rotr              = self:GetClientNumber( "rotr" )
        local recalc            = self:GetClientNumber( "recalc" ) == 1
        local offset            = Vector(offsetx, offsety, offsetz)
        local rot               = Angle(rotp, roty, rotr)
        -- local model          = self:GetClientInfo( "model" )
        
        -- if !model || !util.IsValidModel(model) then return false end
        
        local player = self:GetOwner()
        local ent = trace.Entity
        
        local newvec = ent:GetPos()
        local newang = ent:GetAngles()
        local lastent = ent
        
        undo.Create("ol_stacker")
        
        for i=1, count, 1 do
                if ( !self:GetSWEP():CheckLimit( "props" ) ) then break end
                
                // ********************
                // POSITION CALCULATION
                // ********************
                if i == 1 || (mode == 2 && recalc == true) then
                        // We only calculate this stuff if it's the first item in the stack OR
                        // if recalc is turned on
                        stackdir, height, thisoffset = self:OLStackerCalcPos(lastent, mode, dir, offset)
                end
                
                newvec = newvec + stackdir * height + thisoffset
                newang = newang + rot
                
                // Test to make sure this is inside the level
                if !util:IsInWorld(newvec) then
                        // This check is currently disabled because of a bug in gmod causing
                        // util:IsInWorld to always return false.
                        --break
                end
                
                // Find out if there is an entity on this spot
                local entlist = ents.FindInSphere(newvec,1)
                local bFound = false
                for k, v in pairs(entlist) do
                        if v:IsValid() && v != lastent && v:GetClass() == "prop_physics" && v:GetPos() == newvec && v != self.GhostEntity then
                                bFound = true
                        end
                end
                if bFound then break end
                
                newent = ents.Create("prop_physics")
                        newent:SetModel(ent:GetModel())
                        newent:SetColor(ent:GetColor())
                        newent:SetPos(newvec)
                        newent:SetAngles(newang)
                        newent:Spawn()
                        if freeze then
                                newent:GetPhysicsObject():EnableMotion( false )
                        end
                
                if weld then
                        local weldent = constraint.Weld( lastent, newent, 0, 0, 0 )
                        undo.AddEntity( weldent )
                end
                if nocollide then
                        local nocollideent = constraint.NoCollide(lastent, newent, 0, 0)
                        undo.AddEntity( nocollideent )
                end
                
                lastent = newent
                undo.AddEntity( newent )
                player:AddCount( "props", newent )
                player:AddCleanup( "props", newent )
                
                if PropDefender && PropDefender.Player && PropDefender.Player.Give then
                        PropDefender.Player.Give(player, newent, false)
                end
        end
        
        
        undo.SetPlayer( player )
        undo.Finish()
                
        return true
end

function TOOL:OLStackerCalcPos(lastent, mode, dir, offset)
        local forward = Vector(1,0,0):Angle()
        local pos = lastent:GetPos()
        local ang = lastent:GetAngles()

        local lower, upper = lastent:WorldSpaceAABB( )
        local glower = lastent:OBBMins()
        local gupper = lastent:OBBMaxs()
        
        local stackdir = Vector(0,0,1)
        local height = math.abs(upper.z - lower.z)

        if mode == 1 then // Relative to world
                if dir == 1 then
                        stackdir = forward:Up()
                        height = math.abs(upper.z - lower.z)
                elseif dir == 2 then
                        stackdir = forward:Up() * -1
                        height = math.abs(upper.z - lower.z)
                elseif dir == 3 then
                        stackdir = forward:Forward()
                        height = math.abs(upper.x - lower.x)
                elseif dir == 4 then
                        stackdir = forward:Forward() * -1
                        height = math.abs(upper.x - lower.x)
                elseif dir == 5 then
                        stackdir = forward:Right()
                        height = math.abs(upper.y - lower.y)
                elseif dir == 6 then
                        stackdir = forward:Right() * -1
                        height = math.abs(upper.y - lower.y)
                end
        elseif mode == 2 then // Relative to prop
                forward = ang
                if dir == 1 then
                        stackdir = forward:Up()
                        offset = forward:Up() * offset.X + forward:Forward() * -1 * offset.Z + forward:Right() * offset.Y
                        height = math.abs(gupper.z - glower.z)
                elseif dir == 2 then
                        stackdir = forward:Up() * -1
                        offset = forward:Up() * -1 * offset.X + forward:Forward() * offset.Z + forward:Right() * offset.Y
                        height = math.abs(gupper.z - glower.z)
                elseif dir == 3 then
                        stackdir = forward:Forward()
                        offset = forward:Forward() * offset.X + forward:Up() * offset.Z + forward:Right() * offset.Y
                        height = math.abs(gupper.x - glower.x)
                elseif dir == 4 then
                        stackdir = forward:Forward() * -1
                        offset = forward:Forward() * -1 * offset.X + forward:Up() * offset.Z + forward:Right() * -1 * offset.Y
                        height = math.abs(gupper.x - glower.x)
                elseif dir == 5 then
                        stackdir = forward:Right()
                        offset = forward:Right() * offset.X + forward:Up() * offset.Z + forward:Forward() * -1 * offset.Y
                        height = math.abs(gupper.y - glower.y)
                elseif dir == 6 then
                        stackdir = forward:Right() * -1
                        offset = forward:Right() * -1 * offset.X + forward:Up() * offset.Z + forward:Forward() * offset.Y
                        height = math.abs(gupper.y - glower.y)
                end
                
                -- offset = (stackdir:Angle():Up() * offset.Z) + (stackdir:Angle():Forward() * offset.X) + (stackdir:Angle():Right() * offset.Y)
        end
        
        return stackdir, height, offset
end

function TOOL:RightClick( trace )
        return self:LeftClick( trace )

        // Maybe I'll add this later...
        /*
        if !trace.Entity then return false end
        
        local ent = trace.Entity
        local ply = self:GetOwner()
        
        if ent:GetClass() != "prop_physics" then return false end
        
        local model = ent:GetModel()
        if !model then
                ply:ConCommand("ol_stacker_model \"\"\n")
        else
                ply:ConCommand("ol_stacker_model "..model.."\n")
        end
        
        return true
        */
end

function TOOL.BuildCPanel( CPanel )

        // HEADER
        CPanel:AddControl( "Header", { Text = "#Tool_ol_stacker_name", Description      = "#Tool_ol_stacker_desc" }  )
        
        CPanel:AddControl( "Checkbox", { Label = "Freeze Props", Command = "ol_stacker_freeze" } )
        
        CPanel:AddControl( "Checkbox", { Label = "Weld Props", Command = "ol_stacker_weld" } )
        
        CPanel:AddControl( "Checkbox", { Label = "No Collide Props", Command = "ol_stacker_nocollide" } )

        local params = {Label = "Relative To:", MenuButton = "0", Options = {}}
        params.Options["World"] = {ol_stacker_mode = "1"}
        params.Options["Prop"] = {ol_stacker_mode = "2"}
        
        CPanel:AddControl( "ComboBox", params )

        local params = {Label = "Stack Direction", MenuButton = "0", Options = {}}
        params.Options["Up"] = {ol_stacker_dir = "1"}
        params.Options["Down"] = {ol_stacker_dir = "2"}
        params.Options["Front"] = {ol_stacker_dir = "3"}
        params.Options["Behind"] = {ol_stacker_dir = "4"}
        params.Options["Right"] = {ol_stacker_dir = "5"}
        params.Options["Left"] = {ol_stacker_dir = "6"}
        
        CPanel:AddControl( "ComboBox", params )
        
        CPanel:AddControl( "Slider",  { Label   = "Count",
                                        Type    = "Integer",
                                        Min             = 0,
                                        Max             = 10,
                                        Command = "ol_stacker_count",
                                        Description = "How many props to stack."}        )

        CPanel:AddControl( "Header", { Text = "Advanced Options", Description   = "These options are for advanced users. Leave them all default (0) if you don't understand what they do." }  )

        CPanel:AddControl( "Button",  { Label   = "Reset Advanced Options",
                                        Command = "olstacker_resetoffsets",
                                        Text = "Reset"}  )

        CPanel:AddControl( "Slider",  { Label   = "Offset X (forward/back)",
                                        Type    = "Float",
                                        Min             = 0,
                                        Max             = 1000,
                                        Command = "ol_stacker_offsetx"}  )

        CPanel:AddControl( "Slider",  { Label   = "Offset Y (right/left)",
                                        Type    = "Float",
                                        Min             = 0,
                                        Max             = 1000,
                                        Command = "ol_stacker_offsety"}  )

        CPanel:AddControl( "Slider",  { Label   = "Offset Z (up/down)",
                                        Type    = "Float",
                                        Min             = 0,
                                        Max             = 1000,
                                        Command = "ol_stacker_offsetz"}  )

        CPanel:AddControl( "Slider",  { Label   = "Rotate Pitch",
                                        Type    = "Float",
                                        Min             = 0,
                                        Max             = 360,
                                        Command = "ol_stacker_rotp"}     )

        CPanel:AddControl( "Slider",  { Label   = "Rotate Yaw",
                                        Type    = "Float",
                                        Min             = 0,
                                        Max             = 360,
                                        Command = "ol_stacker_roty"}     )

        CPanel:AddControl( "Slider",  { Label   = "Rotate Roll",
                                        Type    = "Float",
                                        Min             = 0,
                                        Max             = 360,
                                        Command = "ol_stacker_rotr"}     )

        CPanel:AddControl( "Checkbox", { Label = "Stack relative to new rotation", Command = "ol_stacker_recalc", Description = "If this is checked, each item in the stack will be stacked relative to the previous item in the stack. This allows you to create curved stacks." } )

end

if (CLIENT) then

local function ResetOffsets( player, command, arguments )
        -- Reset all of the offset options to 0
        LocalPlayer():ConCommand("ol_stacker_offsetx 0\n")
        LocalPlayer():ConCommand("ol_stacker_offsety 0\n")
        LocalPlayer():ConCommand("ol_stacker_offsetz 0\n")
        LocalPlayer():ConCommand("ol_stacker_rotp 0\n")
        LocalPlayer():ConCommand("ol_stacker_roty 0\n")
        LocalPlayer():ConCommand("ol_stacker_rotr 0\n")
        LocalPlayer():ConCommand("ol_stacker_recalc 0\n")
end

concommand.Add( "olstacker_resetoffsets", ResetOffsets )

end

function TOOL:UpdateGhostStack( ghost, player, ent )

        if ( !ent || !ghost ) then return end
        if ( !ent:IsValid() || !ghost:IsValid() ) then return end

        local mode              = self:GetClientNumber( "mode" )
        local dir               = self:GetClientNumber( "dir" )
        local offsetx           = self:GetClientNumber( "offsetx" )
        local offsety           = self:GetClientNumber( "offsety" )
        local offsetz           = self:GetClientNumber( "offsetz" )
        local rotp              = self:GetClientNumber( "rotp" )
        local roty              = self:GetClientNumber( "roty" )
        local rotr              = self:GetClientNumber( "rotr" )
        local offset            = Vector(offsetx, offsety, offsetz)
        local rot               = Angle(rotp, roty, rotr)
        
        local stackdir, height, thisoffset = self:OLStackerCalcPos(ent, mode, dir, offset)
        
        local newvec = ent:GetPos() + stackdir * height + thisoffset
        local newang = ent:GetAngles() + rot
        
        ghost:SetAngles(newang)
        ghost:SetPos( newvec )
        
        ghost:SetNoDraw( false )

end

function TOOL:Think()
        local player    = self:GetOwner()
        local tr        = utilx.GetPlayerTrace( player, player:GetCursorAimVector() )
        local trace     = util.TraceLine( tr )
        
        
        if trace.Hit then
                local newent    = trace.Entity

                if newent:IsValid() && newent:GetClass() == "prop_physics" && (newent != self.lastent || !self.GhostEntity) then
                        -- Time to change our ghost
                        self:MakeGhostEntity( newent:GetModel(), Vector(0,0,0), Angle(0,0,0) )
                        self.lastent = newent
                end
                if (!self.lastent || !self.lastent:IsValid()) && self.GhostEntity then
                        self:ReleaseGhostEntity()
                end
        end

        if self.lastent != nil && self.lastent:IsValid() then
                self:UpdateGhostStack( self.GhostEntity, player, self.lastent )
        end
end